#include "libjson.h"

enum {
    STS_START = 0,
    STS_END,
    STS_OBJECT_START,
    STS_OBJECT_COLON,
    STS_OBJECT_COMMA,
    STS_ARRAY_START,
    STS_ARRAY_COMMA,
    STS_STRING_START,
    STS_STRING_ESCAPE,
    STS_STRING_HEX1,
    STS_STRING_HEX2,
    STS_STRING_HEX3,
    STS_STRING_HEX4,
    STS_NUMBER_START,
    STS_NUMBER_POSITIVE,
    STS_NUMBER_A,
    STS_NUMBER_B,
    STS_NUMBER_DECIMAL,
    STS_NUMBER_EXPONENT,
    STS_NUMBER_EXPONENT_A,
    STS_NUMBER_EXPONENT_DIGIT,
    STS_NUMBER_EXPONENT_DIGIT_A
};

int json_parse_true( json_task *task );
int json_parse_false( json_task *task );
int json_parse_null( json_task *task );
int json_parse_number( json_task *task );
int json_parse_string( json_task *task );
int json_parse_value( json_task *task, json_str_t *parent );
int json_parse_array( json_task *task, json_str_t *parent );
int json_parse_object( json_task *task, json_str_t *parent );

int json_parse_true( json_task *task ) {
    if( task->count + 3 < task->len &&
        *(task->str + task->count) == 'r' &&
        *(task->str + task->count + 1) == 'u' &&
        *(task->str + task->count + 2) == 'e' ) {
        task->count += 3;
        return 0;
    }
    
    task->err_msg = "expect 'true'";
    return -1;
}

int json_parse_false( json_task *task ) {
    if( task->count + 4 < task->len &&
        *(task->str + task->count) == 'a' &&
        *(task->str + task->count + 1) == 'l' &&
        *(task->str + task->count + 2) == 's' &&
        *(task->str + task->count + 3) == 'e' ) {
        task->count += 4;
        return 0;
    }
    
    task->err_msg = "expect 'false'";
    return -1;
}

int json_parse_null( json_task *task ) {
    if( task->count + 3 < task->len &&
        *(task->str + task->count) == 'u' &&
        *(task->str + task->count + 1) == 'l' &&
        *(task->str + task->count + 2) == 'l' ) {
        task->count += 3;
        return 0;
    }
    
    task->err_msg = "expect 'null'";
    return -1;
}

int json_parse_number( json_task *task ) {
    char ch;
    task->status = STS_NUMBER_START;
    while( ch = *(task->str + task->count) ) {
        task->count ++;

        switch( task->status ) {
            case STS_NUMBER_START:
                if( ch == '-' ) {
                    task->status = STS_NUMBER_POSITIVE;
                } else {
                    task->count --;
                    task->status = STS_NUMBER_POSITIVE;
                }
                break;
            case STS_NUMBER_POSITIVE:
                if( ch == '0' ) {
                    task->status = STS_NUMBER_A;
                } else if ( ch >= '1' && ch <= '9' ) {
                    task->status = STS_NUMBER_B;
                } else {
                    task->err_msg = "expect '0-9'";
                    return -1;
                }
                break;
            case STS_NUMBER_B:
                if ( ch >= '0' && ch <= '9' ) {
                    // do nothing
                } else {
                    task->count --;
                    task->status = STS_NUMBER_A;
                }
                break;
            case STS_NUMBER_A:
                if( ch == '.' ) {
                    task->status = STS_NUMBER_DECIMAL;
                } else {
                    task->count --;
                    task->status = STS_NUMBER_EXPONENT;
                }
                break;
            case STS_NUMBER_DECIMAL:
                if ( ch >= '0' && ch <= '9' ) {
                    // do nothing
                } else {
                    task->count --;
                    task->status = STS_NUMBER_EXPONENT;
                }
                break;
            case STS_NUMBER_EXPONENT:
                if( ch == 'e' || ch == 'E' ) {
                    task->status = STS_NUMBER_EXPONENT_A;
                } else {
                    task->count --;
                    return 0;
                }
                break;
            case STS_NUMBER_EXPONENT_A:
                if( ch == '+' || ch == '-' ) {
                    task->status = STS_NUMBER_EXPONENT_DIGIT;
                } else {
                    task->count --;
                    task->status = STS_NUMBER_EXPONENT_DIGIT;
                }
                break;
            case STS_NUMBER_EXPONENT_DIGIT:
                if ( ch >= '0' && ch <= '9' ) {
                    task->status = STS_NUMBER_EXPONENT_DIGIT_A;
                } else {
                    task->err_msg = "expect '0-9'";
                    return -1;
                }
                break;
            case STS_NUMBER_EXPONENT_DIGIT_A:
                if ( ch >= '0' && ch <= '9' ) {
                    task->status = STS_NUMBER_EXPONENT_DIGIT_A;
                } else {
                    task->count --;
                    return 0;
                }
                break;
            default:
                task->err_msg = "unknown status";
                return -1;
        }
    }
    
    task->err_msg = "unexpect EOF";
    return -1;
}

int json_parse_string( json_task *task ) {
    char ch;
    task->status = STS_STRING_START;
    while( ch = *(task->str + task->count) ) {
        task->count ++;

        switch( task->status ) {
            case STS_STRING_START:
                if( ch == '"' ) {
                    return 0;
                } else if( ch == '\\' ) {
                    task->status = STS_STRING_ESCAPE;
                } else {
                    // do nothing
                }
                break;
            case STS_STRING_ESCAPE:
                if( ch == '"' || ch == '\\' || ch == '/' ||  ch == 'b' ||
                    ch == 'f' || ch == 'n'  || ch == 'r' || ch == 't' ) {
                    task->status = STS_STRING_START;
                } else if( ch == 'u' ) {
                    task->status = STS_STRING_HEX1;
                } else {
                    task->err_msg = "illegal escape";
                    return -1;
                }
                break;
            case STS_STRING_HEX1:
            case STS_STRING_HEX2:
            case STS_STRING_HEX3:
            case STS_STRING_HEX4:
                if( ( ch >= '0' && ch <= '9' ) ||
                    ( ch >= 'a' && ch <= 'f' ) ||
                    ( ch >= 'A' && ch <= 'F' ) ) {
                    if( task->status == STS_STRING_HEX4 ) {
                        task->status = STS_STRING_START;
                    } else {
                        task->status ++;
                    }
                } else {
                    task->err_msg = "expect '0-9', 'a-f', 'A-F'";
                    return -1;
                }
                break;
            default:
                task->err_msg = "unknown status";
                return -1;
        }
    }
    
    task->err_msg = "unexpect EOF";
    return -1;
}

int json_parse_value( json_task *task, json_str_t *parent ) {
    char ch;
    while( ch = *(task->str + task->count) ) {
        task->count ++;

        if( ch == ' ' || ch == '\n' || ch == '\t' ) {
            continue;
        }

        if( ch == '"' ) {
            return json_parse_string( task );
        } else if( ch == '-' || ( ch >= '0' && ch <= '9' ) ) {
            task->count --;
            return json_parse_number( task );
        } else if( ch == '{' ) {
            return json_parse_object( task, parent );
        } else if( ch == '[' ) {
            return json_parse_array( task, parent );
        } else if( ch == 't' ) {
            return json_parse_true( task );
        } else if( ch == 'f' ) {
            return json_parse_false( task );
        } else if( ch == 'n' ) {
            return json_parse_null( task );
        } else {
            task->err_msg = "illegal value";
            return -1;
        }
    }

    task->err_msg = "unexpect EOF";
    return -1;
}

int json_parse_array( json_task *task, json_str_t *parent ) {
    char ch;
    task->status = STS_ARRAY_START;
    
    json_str_t path, value;
    
    path.parent = parent;
    path.str = NULL;
    path.len = 0;

    while( ch = *(task->str + task->count) ) {
        task->count ++;
        
        if( ch == ' ' || ch == '\n' || ch == '\t' ) {
            continue;
        }
        
        switch( task->status ) {
            case STS_ARRAY_START:
                if( ch == ']' ) {
                    return 0;
                } else {
                    task->count --;

                    value.str = task->str + task->count;
                    if( json_parse_value( task, &path ) != 0 ) {
                        return -1;
                    }
                    value.len = task->str + task->count - value.str;

                    // 需要放在 path.len ++ 之前以便正确输出数组下标
                    int i = 0;
                    if( value.str[i] == ' ' || value.str[i] == '\r' || value.str[i] == '\t' ) i++;
                    if( value.str[i] != '[' && value.str[i] != '{' ) {
                        if( task->callback ) task->callback( &path, &value );
                    }

                    path.len ++;
                    task->status = STS_ARRAY_COMMA;
                }
                break;
            case STS_ARRAY_COMMA:
                if( ch == ',' ) {
                    task->status = STS_ARRAY_START;
                } else if( ch == ']' ) {
                    return 0;
                } else {
                    task->err_msg = "expect ',' or ']'";
                    return -1;
                }
                break;
            default:
                task->err_msg = "unknown status";
                return -1;
        }
    }

    task->err_msg = "unexpect EOF";
    return -1;
}

int json_parse_object( json_task *task, json_str_t *parent ) {
    char ch;
    json_str_t path, value;
    
    path.parent = parent;
    path.str = NULL;
    path.len = 0;
    
    task->status = STS_OBJECT_START;

    while( ch = *(task->str + task->count) ) {
        task->count ++;
        
        if( ch == ' ' || ch == '\n' || ch == '\t' ) {
            continue;
        }
        
        switch( task->status ) {
            case STS_OBJECT_START:
                if( ch == '"' ) {
                    path.str = task->str + task->count;
                    if( json_parse_string( task ) != 0 ) {
                        return -1;
                    }
                    path.len = task->str + task->count - path.str - 1;
                    
                    task->status = STS_OBJECT_COLON;
                } else if( ch == '}' ) {
                    // 空对象 {}，忽略
                    return 0;
                } else {
                    task->err_msg = "expect '\"' or '}'";
                    return -1;
                }
                break;
            case STS_OBJECT_COLON:
                if( ch == ':' ) {
                    value.str = task->str + task->count;
                    if( json_parse_value( task, &path ) != 0 ) {
                        return -1;
                    }
                    value.len = task->str + task->count - value.str;
                    
                    task->status = STS_OBJECT_COMMA;
                } else {
                    task->err_msg = "expect ':'";
                    return -1;
                }
                break;
            case STS_OBJECT_COMMA:
                if( ( ch == ',' || ch == '}' ) ) {
                    int i = 0;
                    if( value.str[i] == ' ' || value.str[i] == '\r' || value.str[i] == '\t' ) i++;
                    if( value.str[i] != '[' && value.str[i] != '{' ) {
                        if( task->callback ) task->callback( &path, &value );
                    }
                }
                
                if( ch == ',' ) {
                    task->status = STS_OBJECT_START;
                } else if( ch == '}' ) {
                    return 0;
                } else {
                    task->err_msg = "expect ',' or '}'";
                    return -1;
                }
                break;
            default:
                task->err_msg = "unknown status";
                return -1;
        }
    }
    
    task->err_msg = "unexpect EOF";
    return -1;
}

void json_err_psotion( json_task *task, int *line, int *row ) {
    int p = 0;
    char ch;
    (*line) = 1;
    (*row) = 0;
    while( ch = *(task->str + p) ) {
        p ++;
        
        if( ch == '\n' ) {
            (*line) ++;
            (*row) = 0;
        } else {
            (*row) ++;
        }
        
        if( p == task->count ) break;
    }
}

int json_parser( json_task *task ) {
    char ch;
    json_str_t path;
    
    path.parent = NULL;
    path.str = "root";
    path.len = 4;

    task->err_msg = "OK";
    task->count = 0;
    task->status = STS_START;
    
    if( task->str == NULL || task->len <= 0 ) {
        task->err_msg = "the input string is not specified";
        return -1;
    }
    
    while( ch = *(task->str + task->count) ) {
        task->count ++;
        
        if( ch == ' ' || ch == '\n' || ch == '\t' ) {
            continue;
        }

        switch( task->status ) {
            case STS_START:
                if( ch == '{' ) {
                    if( json_parse_object( task, &path ) != 0 ) {
                        return -1;
                    }
                    task->status = STS_END;
                } else if( ch == '[' ) {
                    if( json_parse_array( task, &path ) != 0 ) {
                        return -1;
                    }
                    task->status = STS_END;
                } else {
                    task->err_msg = "expect '{' or '['";
                    return -1;
                }
                break;
            case STS_END:
                if( task->count == task->len ) {
                    return 0;
                } else {
                    task->err_msg = "expect EOF";
                    return -1;
                }
                break;
            default:
                task->err_msg = "unknown status";
                return -1;
        }
    }
    
    if( task->status == STS_END ) {
        return 0;
    } else {
        return -1;
    }
}
